package executor;


import instrumentation.TrackerCoverage;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.lang.reflect.InvocationTargetException;

import operations.AssignConstant;


import operations.InvokeMethod;
import operations.CreateObject;
import operations.CreateArray;
import ga.Individual;
import operations.Operation;
import ga.GAInfo;
import model.Filter;
import model.StatementKind;
import model.ConstructorDeclared;
import model.MethodDeclared;

import util.GAConfig;
import util.DebugConfig;
import util.Log;

public class ReflectionExecutor {
	/** The internal object repository */
	private final Map<String, Object> repository;	
	private Individual individual;
	private Object retval;
	private GAInfo gaInfo;
	private final TrackerCoverage tracker=	TrackerCoverage.getTracker();

	public static  int execute(GAInfo gaInfo,Individual test) {
		ReflectionExecutor executor = new ReflectionExecutor(gaInfo,test);

		return executor.execute();
	
	}

	public ReflectionExecutor(GAInfo gaInfo,Individual individual){
		
		this.repository = new LinkedHashMap<String, Object>();
		this.individual=individual;
		this.gaInfo=gaInfo;
	}
	public int execute() {
		long startTime=gaInfo.getStartTime();
		long TestTimeout=gaInfo.getTestTimeOut();
		long elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);
		List<Integer> branchs = new ArrayList<Integer>( gaInfo.getMapBranchCondition().keySet());  

		if(DebugConfig.executerClass)
			Log.log("ReflectionExecutor.execute()  ");
		final List<Operation> initialCons  = individual.getInitialConstructors();
		for(Operation op : initialCons ) {
			if (elapsed > TestTimeout) break;
			try {
				if(DebugConfig.executerClass)
					System.out.println(op.toString());
				if (op instanceof CreateObject) createObject(op);
				if(op instanceof AssignConstant) assignConstant(op);
				if(op instanceof CreateArray) createArray(op);

				elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);
				gaInfo.setNoExecution(gaInfo.getNoExecution()+1);
			} catch(Throwable e) {
			//		System.err.println("ReflectionExecutor.execute:"+ e + op.getClass().getName() + " - " + op);
					continue;
			}
		}

		 List<Operation> preOperation= new LinkedList<Operation>();
		final List<Operation> ops  = individual.getStatements();
		for(Operation op : ops ) {
			if (elapsed > TestTimeout) break;

			gaInfo.setNoExecution(gaInfo.getNoExecution()+1);
	//	System.err.println("ReflectionExecutor.execute:" + op);
			if(gaInfo.getNoExecution()%100000==0){
				System.out.println("When " + gaInfo.getNoExecution() + " calls total coverage is " + tracker.getTotalCoveredBranch(branchs));
				if(GAConfig.SaveData) Log.log("When " + gaInfo.getNoExecution() + " calls total coverage is " + tracker.getTotalCoveredBranch(branchs));
			}
			try {
	//			if(DebugConfig.executerClass)
	//			System.out.println(op.toString());
				if (op instanceof CreateObject) createObject(op);
				if(op instanceof InvokeMethod) invokeMethod(op);
				if(op instanceof AssignConstant) assignConstant(op);
				if(op instanceof CreateArray) createArray(op);

			} catch(Throwable e) {
				if(DebugConfig.executerClass)
					System.err.println("ReflectionExecutor.execute:"+ e  + " - " + op);
					continue;
			}
			elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);

			preOperation.add(op);
			Individual temp=new Individual(individual.getInitialConstructors(),preOperation);
			//tracker.bestDistanceReachedBranch(individual.getInitialConstructors(),preOperation);
			tracker.bestDistanceReachedBranch(temp);
		}
		
		return 0;
	}
	/** createObject */
	private void createObject(Operation op) throws Throwable {
		System.out.println("ReflectionExecutor.createObject : "+ op.toString());
		CreateObject cObj=(CreateObject)op;
		  StatementKind statement = cObj.getConstructor();
		 Object[]inputs =cObj.getValues().toArray(); 
			List<Class<?>> parameterTpe=((ConstructorDeclared) statement).getInputTypes();
			Object[] initargs = new Object[parameterTpe.size()];
		 String reference=cObj.getRef();
		 Constructor<?> cons=((ConstructorDeclared) statement).getConstructor();
			for(int i=0; i<parameterTpe.size();i++){
				 Class<?> t = parameterTpe.get(i);
				if( cons.getName().startsWith("java.math")){
					initargs[i]=cObj.getValues().get(i);
					continue;
				}
				if (!Filter.isPrimitiveOrStringType(t)&&inputs[i]!=null)
					initargs[i]=get(inputs[i].toString());
				else
					initargs[i]=cObj.getValues().get(i);
			}
//			if(reference.equals("ClassFilter_1"))
//				System.out.println("");

			Object newObject = null;
			try {
				newObject = cons.newInstance(initargs);

			   }catch(InvocationTargetException invocationException){
			       // e.printStackTrace();
				   return;
			    }catch(Throwable e){
			    	return;
			       // e.printStackTrace();
			    }
			if(newObject.toString().contains("com.sun.jdi.InvocationException")) return;
			if(reference!=null && newObject!=null)
				set(reference,newObject);
	}
	private void createArray(Operation op){
		  CreateArray array=((CreateArray)op);
		  
		   Object theArray = Array.newInstance(array.getElementType(), array.getLength());
		   List<Object> inputList=array.getInputVariables();
		    for (int i = 0; i < array.getLength(); i++)
		    	if(Filter.isPrimitiveOrStringType(array.getElementType()))
		    			Array.set(theArray, i, inputList.get(i));
		    	else
		    		Array.set(theArray, i, get(inputList.get(i).toString()));

		    set(array.getReference(), theArray);

		
		
	}
	private void invokeMethod(Operation op)  {
		InvokeMethod  	iMethod=(InvokeMethod) op;
		StatementKind statement = iMethod.getMethod();
		Object[]inputs =iMethod.getValues().toArray(); 
		final String retrun_=iMethod.getReturn();
		final  List<Class<?>> replacementObjectParam= iMethod.getReplacementObjectParam();
		final String receiver = iMethod.getThis();
		final Object baseObject;

		if(receiver != null)
			baseObject = get(receiver);
		else
			baseObject = null;

		
		List<Class<?>> parameterTpe=((MethodDeclared) statement).getInputTypes();
		for(int i=0; i<parameterTpe.size();i++){
			 Class<?> t = parameterTpe.get(i);
			 if(Filter.isObjectType(t) && replacementObjectParam!=null)
				t= replacementObjectParam.get(i);
			if (!Filter.isPrimitiveOrStringType(t)&&inputs[i]!=null)
				inputs[i]=get(inputs[i].toString());
		}
		
		
		final Method m = ((MethodDeclared) statement).getMethod();
		
		try {
			this.retval = m.invoke(baseObject, inputs);
			if (this.retval != null)
				set(retrun_, this.retval);
		} catch (InvocationTargetException e) {
			if(DebugConfig.executerClass)
				System.err.println("ReflectionExcution.invokeMethod: "+op.toString() +" "+  e.getCause());
			 return; // continues normal execution in case of exception in
			// called
	//		System.exit(1);
		} catch (IllegalAccessException e) {
			System.err.println("Illegal access error.");
			System.exit(1);
		}	
	}
	private void assignConstant(Operation op)  {
		AssignConstant aConstant=(AssignConstant)op;
		Field field=aConstant.getValue();
		String reference=aConstant.getRefrence();
		if(field == null)
			set(reference, null);
		else{
			try{
				Object newObject = field.get(null);
				set(reference, newObject);
		} catch(Throwable e) {
			//System.err.println("ReflectionExecutor.assignConstant :"+ e.getMessage());
		}

		}

	}
	private void set(String reference, Object value)  {
		if(GAConfig.DEBUG) {
			if(reference == null) {
				new NullPointerException("objRef cannot be null");
				return ;
			}
			if(repository == null) {
				new NullPointerException("repository cannot be null");
				return ;
			}
		}
		
		this.repository.put(reference, value);
	}
	private Object get(String reference)  {
		if(GAConfig.DEBUG) {
			if(reference == null) {
				new NullPointerException("objRef cannot be null");
				return null;
			}
			if(repository == null) {
				new NullPointerException("repository cannot be null");
				return null;
			}
		}
		return this.repository.get(reference);
	}

	 static final class NotCaughtIllegalStateException extends IllegalStateException {
		    private static final long serialVersionUID = -7508201027241079866L;
		    NotCaughtIllegalStateException(String msg) { super(msg); }
		  }
	
	
}
